// addrspace.cc 
//	Routines to manage address spaces (executing user programs).
//
//	In order to run a user program, you must:
//
//	1. link with the -N -T 0 option 
//	2. run coff2noff to convert the object file to Nachos format
//		(Nachos object code format is essentially just a simpler
//		version of the UNIX executable object code format)
//	3. load the NOFF file into the Nachos file system
//		(if you haven't implemented the file system yet, you
//		don't need to do this last step)
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "system.h"
#include "addrspace.h"
//#include "noff.h"
#include "table.h"
#include "synch.h"

extern "C" { int bzero(char *, int); };

Table::Table(int s) : map(s), table(0), lock(0), size(s) {
    table = new void *[size];
    lock = new Lock("TableLock");
}

Table::~Table() {
    if (table) {
	delete table;
	table = 0;
    }
    if (lock) {
	delete lock;
	lock = 0;
    }
}

void *Table::Get(int i) {
    // Return the element associated with the given if, or 0 if
    // there is none.

    return (i >=0 && i < size && map.Test(i)) ? table[i] : 0;
}

int Table::Put(void *f) {
    // Put the element in the table and return the slot it used.  Use a
    // lock so 2 files don't get the same space.
    int i;	// to find the next slot

    lock->Acquire();
    i = map.Find();
    lock->Release();
    if ( i != -1)
	table[i] = f;
    return i;
}

void *Table::Remove(int i) {
    // Remove the element associated with identifier i from the table,
    // and return it.

    void *f =0;

    if ( i >= 0 && i < size ) {
	lock->Acquire();
	if ( map.Test(i) ) {
	    map.Clear(i);
	    f = table[i];
	    table[i] = 0;
	}
	lock->Release();
    }
    return f;
}

//----------------------------------------------------------------------
// SwapHeader
// 	Do little endian to big endian conversion on the bytes in the 
//	object file header, in case the file was generated on a little
//	endian machine, and we're now running on a big endian machine.
//----------------------------------------------------------------------

static void 
SwapHeader (NoffHeader *noffH)
{
	noffH->noffMagic = WordToHost(noffH->noffMagic);
	noffH->code.size = WordToHost(noffH->code.size);
	noffH->code.virtualAddr = WordToHost(noffH->code.virtualAddr);
	noffH->code.inFileAddr = WordToHost(noffH->code.inFileAddr);
	noffH->initData.size = WordToHost(noffH->initData.size);
	noffH->initData.virtualAddr = WordToHost(noffH->initData.virtualAddr);
	noffH->initData.inFileAddr = WordToHost(noffH->initData.inFileAddr);
	noffH->uninitData.size = WordToHost(noffH->uninitData.size);
	noffH->uninitData.virtualAddr = WordToHost(noffH->uninitData.virtualAddr);
	noffH->uninitData.inFileAddr = WordToHost(noffH->uninitData.inFileAddr);
}

//----------------------------------------------------------------------
// AddrSpace::AddrSpace
// 	Create an address space to run a user program.
//	Load the program from a file "executable", and set everything
//	up so that we can start executing user instructions.
//
//	Assumes that the object code file is in NOFF format.
//
//	"executable" is the file containing the object code to load into memory
//
//      It's possible to fail to fully construct the address space for
//      several reasons, including being unable to allocate memory,
//      and being unable to read key parts of the executable.
//      Incompletely consretucted address spaces have the member
//      constructed set to false.
//----------------------------------------------------------------------

AddrSpace::AddrSpace(OpenFile *executable) : fileTable(MaxOpenFiles) {

  addrSpaceLock = new Lock("Address Space Lock");


   addrSpaceLock->Acquire();



   //NoffHeader noffH;
       unsigned size;

	// Don't allocate the input or output to disk files
	fileTable.Put(0);
	fileTable.Put(0);

	executable->ReadAt((char *)&noffH, sizeof(noffH), 0);
	if ((noffH.noffMagic != NOFFMAGIC) && 
		 (WordToHost(noffH.noffMagic) == NOFFMAGIC))
    	SwapHeader(&noffH);
	ASSERT(noffH.noffMagic == NOFFMAGIC);

	size = noffH.code.size + noffH.initData.size + noffH.uninitData.size ;
	numPages = divRoundUp(size, PageSize) + divRoundUp(UserStackSize,PageSize);
	size = numPages * PageSize;


	ASSERT(numPages <= NumPhysPages);		// check we're not trying
	// to run anything too big --
	// at least until we have
	// virtual memory

	DEBUG('a', "Initializing address space, num pages %d, size %d\n", 
			numPages, size);

	//Create a page table to store the number of pages needed for the executable
	pageTable = new TranslationEntry[numPages];
	
	MMLock->Acquire();

	//For each page, look for a page that is available in the main memory.
	//Once found
 	for (unsigned int i = 0; i < numPages; i++) {
	  int loc =  mainMemory->Find();
	  if(loc != -1 ){

	      pageTable[i].virtualPage = i;	// for now, virtual page # = phys page #
	      pageTable[i].physicalPage = loc;
	      pageTable[i].valid = TRUE;
	      pageTable[i].use = FALSE;
	      pageTable[i].dirty = FALSE;
	      pageTable[i].readOnly = FALSE;  // if the code segment was entirely on 
	      // a separate page, we could set its 
	      // pages to be read-only

	      if(i < divRoundUp(noffH.code.size + noffH.initData.size, PageSize)){
		executable->ReadAt(&(machine->mainMemory[loc*PageSize]),
				   PageSize, noffH.code.inFileAddr + PageSize*i);
	      }
	  }
	  else{
	    //Not enough space, terminate nachos
	    printf("AddrSpace(): INSUFFICIENT MEMORY!!");
	    interrupt->Halt();
	  }
	}

	
	MMLock->Release();


	addrSpaceLock->Release();

}

  //-------------- PROJECT 3 CODE ---------------
//   addrSpaceLock = new Lock("Address Space Lock");
  
//   addrSpaceLock->Acquire();

//   this->execFile = executable;

//   //NoffHeader noffH;
//   unsigned size;

//   // Don't allocate the input or output to disk files
//   fileTable.Put(0);
//   fileTable.Put(0);

//   executable->ReadAt((char *)&noffH, sizeof(noffH), 0);
//   if ((noffH.noffMagic != NOFFMAGIC) && 
//       (WordToHost(noffH.noffMagic) == NOFFMAGIC))
//     SwapHeader(&noffH);
//   ASSERT(noffH.noffMagic == NOFFMAGIC);

//   size = noffH.code.size + noffH.initData.size + noffH.uninitData.size ;
//   numCodePages = noffH.code.size / PageSize;
//   numPages = divRoundUp(size, PageSize) + divRoundUp(UserStackSize,PageSize);
//   size = numPages * PageSize;


//   //	ASSERT(numPages <= NumPhysPages);		// check we're not trying
//   // to run anything too big --
//   // at least until we have
//   // virtual memory

//   DEBUG('a', "Initializing address space, num pages %d, size %d\n", 
// 	numPages, size);

//   //Create a page table to store the number of pages needed for the executable
//   printf("numPages of code %d initData %d total pages %d\n",divRoundUp(noffH.code.size,PageSize),divRoundUp(noffH.initData.size,PageSize),numPages);

//   pageTable = new TranslationEntry[numPages];
//   pageTableInfo = new PageTableInfo[numPages]; //Additional information for each page of the page table
//   for (unsigned int i = 0; i < numPages; i++) {
//     if(i < numPages){
//       pageTableInfo[i].location = InExecutable;
//       pageTableInfo[i].swapFilePosition = -1;

//       pageTable[i].virtualPage = i;
//       pageTable[i].physicalPage = NumPhysPages + 1;
//       pageTable[i].valid = FALSE;
//       pageTable[i].use = FALSE;
//       pageTable[i].dirty = FALSE;
//       pageTable[i].readOnly = FALSE;
	    
//     }
//   }



//---------------------- PROJECT 3 CODE END -------------------

  //No preloading of code and data

  //PROJECT 2 STUFF:
  /*
    MMLock->Acquire();

    //For each page, look for a page that is available in the main memory.
    //Once found
    pageTable = new TranslationEntry[numPages];
    for (unsigned int i = 0; i < numPages; i++) {
    int loc =  mainMemory->Find();
    if(loc != -1 ){

    pageTable[i].virtualPage = i;	
    pageTable[i].physicalPage = loc;
    pageTable[i].valid = TRUE;
    pageTable[i].use = FALSE;
    pageTable[i].dirty = FALSE;
    pageTable[i].readOnly = FALSE;  // if the code segment was entirely on 
    // a separate page, we could set its 
    // pages to be read-only

	     
    //add the new entry to the IPT
    //we must do it here because we will 
    //still pagefault on rest of data
    ipt->setElement(i,loc,this);

    if(i < divRoundUp(noffH.code.size + noffH.initData.size, PageSize)){
    //	executable->ReadAt(&(machine->mainMemory[loc*PageSize]),PageSize, noffH.code.inFileAddr + PageSize*i);
    execFile->ReadAt(&(machine->mainMemory[loc*PageSize]),PageSize, noffH.code.inFileAddr + PageSize*i);
    }
    }
    else{
    //Not enough space, terminate nachos
    printf("AddrSpace(): INSUFFICIENT MEMORY!!");
    interrupt->Halt();
    }
    }

	
    MMLock->Release();

	
  addrSpaceLock->Release();

}  */

//----------------------------------------------------------------------
// AddrSpace::~AddrSpace
//
// 	Dealloate an address space.  release pages, page tables, files
// 	and file tables
//----------------------------------------------------------------------

  AddrSpace::~AddrSpace(){
  delete pageTable;
  delete pageTableInfo;
  delete addrSpaceLock;
  numCodePages = -1;
  }

//----------------------------------------------------------------------
// AddrSpace::InitRegisters
// 	Set the initial values for the user-level register set.
//
// 	We write these directly into the "machine" registers, so
//	that we can immediately jump to user code.  Note that these
//	will be saved/restored into the currentThread->userRegisters
//	when this thread is context switched out.
//----------------------------------------------------------------------

void
AddrSpace::InitRegisters()
{
    int i;

    for (i = 0; i < NumTotalRegs; i++)
	machine->WriteRegister(i, 0);

    // Initial program counter -- must be location of "Start"
    machine->WriteRegister(PCReg, 0);	

    // Need to also tell MIPS where next instruction is, because
    // of branch delay possibility
    machine->WriteRegister(NextPCReg, 4);

   // Set the stack register to the end of the address space, where we
   // allocated the stack; but subtract off a bit, to make sure we don't
   // accidentally reference off the end!
    machine->WriteRegister(StackReg, numPages * PageSize - 16);
    DEBUG('a', "Initializing stack register to %x\n", numPages * PageSize - 16);
}

//----------------------------------------------------------------------
// AddrSpace::SaveState
// 	On a context switch, save any machine state, specific
//	to this address space, that needs saving.
//
//	For now, nothing!
//----------------------------------------------------------------------

void AddrSpace::SaveState() 
{

  //printf("-----CONTEXT SWITCHED: SAVING STATE------\n");
  
  //it makes sense to make sure the dirty bits are set for the ipt
  //that are set in the tlb
  //thus propogate the dirty bits from the tlb to the ipt

  //printf("Context Switched -");

  //-------------- PROJECT 3 CODE -----------------

//    for(int i = 0; i < 4 ; i++){
//      if( machine->tlb[i].dirty && machine->tlb[i].valid){
//       ipt->setDirtyBit(machine->tlb[i].virtualPage);
//     }
//     if( machine->tlb[i].use && machine->tlb[i].valid){
//       ipt->setUseBit(machine->tlb[i].virtualPage);
//     }
//   }
   
//    ipt->lastAddrspace = (int)this;
   
}

//----------------------------------------------------------------------
// AddrSpace::RestoreState
// 	On a context switch, restore the machine state so that
//	this address space can run.
//
//      For now, tell the machine where to find the page table.
//----------------------------------------------------------------------

void AddrSpace::RestoreState() 
{


  //-------------- PROJECT 3 CODE ---------------


  //On a context switch, we would normally invalidate all entries of the TLB
  //But if the thread shares the same address space as the previous thread,
  //Then we don't need to invalidate the TLB, which will speed things up.
//   if((int)this != ipt->lastAddrspace){
//     //printf("Different address space from last thread thus invalidating TLB entries\n\n");
//     for(int i = 0; i < 4; i++){
//       machine->tlb[i].valid = false;
//     }
//   }
  //else printf("\n");
  


  //--------------- PROJECT 2 CODE ----------------

  machine->pageTable = pageTable;



  //-------------- EXISTING CODE ---------------
  machine->pageTableSize = numPages;
}


//method to clear a pageTable entry
void AddrSpace::Clear(int i)
{
  
  addrSpaceLock->Acquire();

   pageTable[i].virtualPage = i;       
   pageTable[i].physicalPage = -1;
   pageTable[i].valid = FALSE;
   pageTable[i].use = FALSE;
   pageTable[i].dirty = FALSE;
   pageTable[i].readOnly = FALSE;

   addrSpaceLock->Release();
 
}

//find the next available pageTable entry to set
//set its physical page to physicalPage
int AddrSpace::Find(int physicalPage)
{
  addrSpaceLock->Acquire();


    for (unsigned int i = 0; i < numPages; i++) {
      if(pageTable[i].valid == FALSE)
	{
	   pageTable[i].physicalPage = physicalPage;
	   pageTable[i].valid = TRUE;
	   
	   return i;
	}
    }


    addrSpaceLock->Release();

    return (-1);
}

//free main memory for the entire address space
void AddrSpace::freeMainMemory()
{
  MMLock->Acquire();
  addrSpaceLock->Acquire();

   for (int i = 0; i < numPages; i++) {
     mainMemory->Clear(pageTable[i].physicalPage);
    }

  // ------------------ PROJECT 3 CODE -------------------


//    for (unsigned int i = 0; i < numPages; i++) {
//      if(pageTable[i].valid = TRUE && 
// 	pageTable[i].physicalPage <= NumPhysPages){
//        mainMemory->Clear(pageTable[i].physicalPage);
//      }
//      else
//        {
// 	 //	 printf("%s: In AddrSpace::freeMainMemory() ppn of vpn %d larger than largest possible value\n",currentThread->getName(),pageTable[i].virtualPage);
//        }
//     }



   //-------------------- PROJECT 3 CODE END ------------------

   addrSpaceLock->Release();
   MMLock->Release();

}


//TEMP: not used
void AddrSpace::freeSwap(){
  for(int i = 0; i < numPages; i++){
    if(pageTableInfo[i].swapFilePosition != -1){
      swapFileBitmap->Clear(pageTableInfo[i].swapFilePosition);
    }
  }
}


int AddrSpace::NewThreadStack(int *bitmapLoc){
  printf("IN  AddrSpace::NewThreadStack(int *bitmapLoc)\n");
  addrSpaceLock->Acquire();
  
  TranslationEntry *newPageTable = new TranslationEntry[numPages + 8];
  PageTableInfo *newPageTableInfo = new PageTableInfo[numPages + 8];

  //TLB version
   //Set the data for the new page table
  for(unsigned int i=0; i<numPages + 8; i++){
    //Copy the old contents into the new page table first
    if(i < numPages){
      newPageTableInfo[i] = pageTableInfo[i];
      newPageTable[i] = pageTable[i];
    }
    //Then set the new 8 pages with the physical page number
    else if(i < numPages + 8){
      newPageTableInfo[i].location = InExecutable;
      newPageTableInfo[i].swapFilePosition = -1;

      newPageTable[i].virtualPage = i;	
      newPageTable[i].physicalPage =  -1;
      newPageTable[i].valid = TRUE;
      newPageTable[i].use = FALSE;
      newPageTable[i].dirty = FALSE;
      newPageTable[i].readOnly = FALSE;


     
      // printf("vpn %d added from  AddrSpace::NewThreadStack()\n",i);
    }
    //Something is really not right if the else statment executes
    else {
      printf("AddrSpace::NewThreadStack() - NEW PAGE TABLE ERROR!\n");
      interrupt->Halt();
    }
  }

  numPages += 8;


  //PROJECT 2 Version:
  /*

  //Make a new page table that is 8 pages bigger
 
  //Set the data for the new page table
  for(unsigned int i=0; i<numPages + 8; i++){
    //Copy the old contents into the new page table first
    if(i < numPages){
      newPageTable[i] = pageTable[i];
    }
    //Then set the new 8 pages with the physical page number
    else if(i < numPages + 8){
      newPageTable[i].virtualPage = i;	// for now, virtual page # = phys page #
      newPageTable[i].physicalPage = bitmapLoc[i - numPages];
      newPageTable[i].valid = TRUE;
      newPageTable[i].use = FALSE;
      newPageTable[i].dirty = FALSE;
      newPageTable[i].readOnly = FALSE;


      //add the new entry to the IPT
      ipt->setElement(i,newPageTable[i].physicalPage,this);
      // printf("vpn %d added from  AddrSpace::NewThreadStack()\n",i);
    }
    //Something is really not right if the else statment executes
    else {
      printf("AddrSpace::NewThreadStack() - NEW PAGE TABLE ERROR!\n");
      interrupt->Halt();
    }
  }

  numPages += 8;
  */
  pageTable = newPageTable;
  pageTableInfo = newPageTableInfo;
  addrSpaceLock->Release();

  return numPages;
}


//Validate the virtual address: make sure it does not exceed the bounds of the virtual address
bool AddrSpace::validateVaddr(int vaddr){
  //  printf("AddrSpace::validateVaddr() vaddr is %d and total is %d\n",vaddr,numPages*PageSize);
  if(vaddr > numPages*PageSize || vaddr < 0)
    {
      printf("Invalid vaddr: %d\t  address space size: %d\n", vaddr, numPages*PageSize);
      return false;
    }
  else return true;
}


// int AddrSpace::getPhysAddr(int vPage) {
//   for(int i = 0; i < numPages; i++){
//     if(pageTable[i].virtualPage == vPage)
//       {
// 	printf("returned ppn %d\n",pageTable[i].physicalPage);
// 	return pageTable[i].physicalPage;
//       }
//   }
//   //else
//       printf("AddrSpace::getPhysAddr() did not find a ppn\n");
   
// }
